package com.config;

import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.endpoint.*;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.*;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.KeyStore;
import java.time.Duration;

/**
 * @author Joe Grandja
 * @since 0.0.1
 */
@Setter
@Slf4j
@Configuration
@AutoConfigureBefore(WebConfigForFlux.class)
public class WebClientConfigForFlux {

    @Value("${trust-store-pem}")
    private Resource trustStorePEM;

    @Value("${trust-store-password}")
    private String trustStorePassword;

    @Value("${key-store}")
    private Resource keyStore;

    @Value("${key-store-password}")
    private String keyStorePassword;

    @Value("${key-password}")
    private String keyPassword;

    public SslContext getTwoWaySslContext() {

        try {
            InputStream keyStoreFileInputStream = Files.newInputStream(Paths.get(keyStore.getURI()), StandardOpenOption.READ);
            KeyStore keyStore = KeyStore.getInstance("pkcs12");
            keyStore.load(keyStoreFileInputStream, keyStorePassword.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, keyPassword.toCharArray());

            return SslContextBuilder.forClient()
                    .keyManager(keyManagerFactory)
                    .trustManager(Files.newInputStream(Paths.get(trustStorePEM.getURI()), StandardOpenOption.READ))
                    .build();

        } catch (Exception e) {
            log.error("An error has occurred: ", e);
        }

        return null;
    }

    public WebClient oauth2ClientRestOperations() {
        reactor.netty.http.client.HttpClient secure = reactor.netty.http.client.HttpClient
                .create().secure(sslContextSpec -> sslContextSpec.sslContext(getTwoWaySslContext()));
        ReactorClientHttpConnector connector = new ReactorClientHttpConnector(secure);
        WebClient build = WebClient.builder().clientConnector(connector).build();
        return build;
    }

    @Bean
    public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
        DefaultReactiveOAuth2UserService userService = new DefaultReactiveOAuth2UserService();
        userService.setWebClient(oauth2ClientRestOperations());
        return userService;
    }

    @Bean
    public OidcReactiveOAuth2UserService oidcUserService() {
        OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService();
        userService.setOauth2UserService(oauth2UserService());
        return userService;
    }

    @Bean
    WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultOAuth2AuthorizedClient(true);
        reactor.netty.http.client.HttpClient secure = reactor.netty.http.client.HttpClient
                .create().secure(sslContextSpec -> sslContextSpec.sslContext(getTwoWaySslContext()));
        ReactorClientHttpConnector connector = new ReactorClientHttpConnector(secure);
        return WebClient.builder().clientConnector(connector).filter(oauth).build();
    }
    @Bean
    public ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient() {
        WebClientReactiveRefreshTokenTokenResponseClient tokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient();
        tokenResponseClient.setWebClient(oauth2ClientRestOperations());
        return tokenResponseClient;
    }
    @Bean
    ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> requestReactiveOAuth2AccessTokenResponseClient(){
        WebClientReactiveAuthorizationCodeTokenResponseClient client = new WebClientReactiveAuthorizationCodeTokenResponseClient();
        client.setWebClient(oauth2ClientRestOperations());
        return client;
    }

    @Primary
    @Bean
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager(ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder().
                authorizationCode()
                .refreshToken(refreshToken ->
                        refreshToken.accessTokenResponseClient(refreshTokenTokenResponseClient())).
                        clientCredentials().password().build();
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        return authorizedClientManager;
    }





}